package lock

import (
	"context"
	"github.com/redis/go-redis/v9"
	uuid "github.com/satori/go.uuid"
	"time"
)

// 公平锁

type FairSync struct {
	lock   *Lock
	unCh   chan int32
	key    string
	expire int64
}

// NewFairSync 初始化一个公平锁对象
// key 设置一个锁的键，同一个锁的体系里，需要使用相同的键名。
func NewFairSync(client *redis.Client, key string) SyncInterface {
	keyList := key + ":list"
	return &FairSync{
		lock:   NewLock(client, []string{key, keyList}),
		unCh:   make(chan int32, 0),
		key:    key,
		expire: Expire,
	}
}

// Lock 加锁
func (rdb *FairSync) Lock(ctx context.Context) error {
	rdb.expire = rdb.lock.Deadline(ctx)
	uid := uuid.NewV4().String()
	result, err := rdb.lock.Acquire(ctx, FairSyncLockScript, DefaultValue, rdb.expire*Second, uid)
	if err != nil {
		return err
	}

	acquire := result.(string)
	// 获取到锁
	if acquire == ResultOK {
		go rdb.cas(ctx)
		return nil
	}
	// 排队等待
	if rdb.lock.Wait(ctx, acquire) {
		return rdb.Lock(ctx)
	}
	return nil
}

// UnLock 解锁
func (rdb *FairSync) UnLock(ctx context.Context) error {
	// 断开自旋
	defer func() {
		rdb.unCh <- 1
	}()
	return rdb.un(ctx)
}

// 自旋
func (rdb *FairSync) cas(ctx context.Context) {
	select {
	case <-rdb.unCh:
		return
	case <-ctx.Done():
		// 超时处理 直接释放锁
		_ = rdb.UnLock(context.TODO())
		return
	case <-time.After(time.Second * time.Duration(rdb.expire/2)):
		// 自旋增加失效的时间
		err := rdb.lock.Refresh(ctx, rdb.expire)
		if err != nil {
			// 失败直接断开连接
			return
		}
		rdb.cas(ctx)
	}
}

// 递归
func (rdb *FairSync) un(ctx context.Context) error {
	result, err := rdb.lock.Un(ctx, UnFairSyncLockScript, DefaultValue)
	if err != nil {
		return err
	}
	if result.(string) == ResultNEXT {
		return rdb.un(ctx)
	}
	return nil
}
